<?php

/**
 * @package chuixue.cn comic downloder
 * @author torn
 * @version 0.9
 * @create_date 14.6.1
 * @encoding gb2312
 * @link zip http://www.phpconcept.net/pclzip/news
 * @link snoopy http://snoopy.sourceforge.net/
 * @update_log 15.06.24 调整数据逻辑
 * @update_log 15.06.20 完善同步功能
 * @update_log 15.06.15 去除模态框动画 增加搜索路线切换,15.03.21  ···
 */
/* xindm 漫画下载工具类 */

class Xindm_tool {

	public static $debug = FALSE; // 调试模式(下载/保存错误 [TRUE]中断)
	public static $hostName;
	public static $hostTitle;
	public static $hostComicUrl;
	public static $hostUrl = 'http://cartoon.youzu88.com/manhuatuku/'; // 漫画图链服务器 http://mh.xindm.cn
	public static $saveDir = 'chuixue_comic'; // 漫画保存主目录(末尾不带'/')
	public static $retryNum = 3; // 失败重试下载次数
	public static $threadNum = 3; // 以N个进程进行下载 数值为[1-5]
	public static $saveType = 'file'; // 存储方式 [file]文件 [database]文件 数据库
	public static $zhFlag = FALSE; // 漫画主目录是否保存为中文
	public static $url_gather; // 漫画资源url
	
	### 临时
	public static $version = '0.9';
	public static $httpDatas; // http数据请求参数
	public $uniqid; // 卷集防重复运行唯一标识
	public $ridType = 'all'; //下载模式 [all]默认全选 [any]默认空选
	public $runMode = 'foreground'; // 运行模式 [foreground]前台 [background]后台
	public $packageFlag = FALSE; // 是否按卷集打包漫画 (zip)
	public $checkInfo; // 查询漫画标记
	public $comicName; // 漫画名称
	public $comicTitle; // 漫画标题
	public $saveName; // 保存目录
	public $comicList; // 漫画主页地址
	public $comicData; // 漫画卷集数据
	public $author; // 漫画家
	public $rids; // 排除(all)/选择(any) 的卷集
	public $lists; // 漫画全部卷集
	public $listData; // 需下载的卷集
	public $rePics; // 下载失败的图片
	public $passImgs; // 跳过/已下载的图片
	public $param; // 初始化参数 数组
	public $encodeName; // rawurlencode(漫画名称)
	public $dataPath; // 存放漫画列表文件路径
	public $viewFile; // 目录视图文件字符串
	public $retryFlag = FALSE; // 重试下载状态标识
	public $complateFlag = FALSE; // 是否正常完成脚本
	public $fpLock = FALSE; // 文件锁 流
	public $startTime; // 开始时间
	public $endTime; // 结束时间
	public $picCount = 0; // 需要下载图片 统计
	public $imgCount = 0; // 实际下载图片 统计
	public $passCount = 0; // 跳过/已下载图片 统计
	public $listCount = 0; // 下载卷集数 统计
	public $retryCount = 0; // 重试失败下载 统计

	public function __construct($param) {
		$this->param = is_array($param) ? $param : FALSE;
		$this->init();
	}

	public function __destruct() {
		if ($this->runMode != 'background') return FALSE;
		$lostFiles = $this->rePics ? implode(", ", $this->rePics) : 'Null';
		$rePicsNum = count($this->rePics);
		$this->passCount = count($this->passImgs);
		//$this->retryDownload();
		$this->endTime = date('Y-m-d H:i:s');
		$runTime = strtotime($this->endTime) - strtotime($this->startTime);
		$runMinute = floor($runTime / 60);
		$runSecond = $runTime - $runMinute * 60;
		$saveDir = self::$saveDir;
		$status = $this->complateFlag === TRUE ? 'All' : 'ANY';
		echo "<div id='destruct'>
			<hr>status: $status is ok !<br>"
		. "time: $this->startTime => $this->endTime<br>"
		. "runTime: {$runMinute}m : {$runSecond}s<br>"
		. "comicTitle: $this->comicTitle<br />"
		. "picCount: $this->picCount<br>"
		. "downCount: $this->imgCount<br>"
		. "listCount: $this->listCount<br>"
		. "passCount: $this->passCount<br>"
		. "lostFiles: [$rePicsNum] $lostFiles<br>"
		. "retryCount: $this->retryCount<hr>"
		. "<a href='{$saveDir}/{$this->comicTitle}/' target='_blank'>checkFile</a>"
		. "</div>"
		. "<script>end();</script>";
	}

	/* 初始化 */

	protected function init() {
		self::loadConfig();
		$this->setParam();
		$this->pretreatment();
	}

	static protected function loadConfig() {
		if (is_array(self::$url_gather)) return false;
		$conf = System::$config['Xindm_tool'];
		self::$saveDir = $conf['saveDir'];
		self::$hostTitle = $conf['hostTitle'];
		self::$hostComicUrl = $conf['hostComicUrl'];
		self::$hostName = $conf['hostName'];
		self::$hostUrl = $conf['hostUrl'];
		self::$saveType = $conf['saveType'];
		self::$threadNum = $conf['threadNum'];
		self::$retryNum = $conf['retryNum'];
		self::$debug = $conf['debug'];
		//self::$zhFlag = $conf['zhFlag'];
		self::$url_gather = array(
			'comic_info' => self::$hostComicUrl, // 'http://www.' . self::$hostName . '/manhua/',
			'index_web' => 'http://www.' . self::$hostName,
			'search_web' => 'http://www.' . self::$hostName . '/search/',
			'search_wap' => 'http://m.' . self::$hostName . '/seach',
			'comic_all' => 'http://www.' . self::$hostName . '/manhuadaquan/',
		);
	}

	/* 静态方法加载器: 预加载loadConfig */
	static public function loadStaticMethod($method, $param = array()) {
		self::loadConfig();
		return call_user_func_array(array(__CLASS__, $method), $param);
	}
	/* 动作 流程 */

	public function downAction() {
		//$this->checkPicAction();
		$this->getList();
		$this->setComicData();
		$this->getComicByLists();
		$this->retryDownload();
		$this->complateFlag = TRUE;
	}

	public function readVolAction() {
		$dataPath = $this->dataPath = self::$saveDir . '/' . $this->saveName . '/lists_bak.txt';
		if (file_exists($dataPath)) {
			$comicData_foo = file_get_contents($dataPath);
			$comicData_temp = unserialize($comicData_foo);
			if (is_array($comicData_temp) && $comicData_temp['data'][$this->rids[0]]) return $comicData_temp['data'];
		}
		$this->getList();
		$this->setComicData();
		$this->comicPicTotal($this->listData);
		return $this->comicData['data'];//[0]['pics'];
	}

	/* 预处理 */

	protected function pretreatment() {
		//header("Content-type:text/html;charset=GB2312");
		$this->startTime = date('Y-m-d H:i:s');
		ignore_user_abort(TRUE);
		set_time_limit(5110);
		clearstatcache(); // 清除文件缓存
		/*if ($this->runMode == 'foreground') {
			//echo str_repeat(" ", 4096);
			ob_flush() . flush();
		} else if ($this->runMode == 'background') {
			self::$saveType == 'database' && new Database;
		}*/
	}

	/* 配置参数 */

	protected function setParam() {
		$p = $this->param;
		if ($p == FALSE) return FALSE;
		//$p == FALSE && die('error: param is empty !');
		$this->comicName = $p['comicname']; //!= '' ? rawurldecode($p['comicname']) : die('error: comicName[' . $p['comicname'] . ']');
		//preg_match('/(?:http:\/\/www\.)?xindm\.cn\/mh\/([\s\S]+)\/?$/is', trim($this->comicName, '/'), $comicName) && $this->comicName = $comicName[1];
		//isset($p['hosturl']) && self::$hostUrl = $p['hosturl'];
		$gbkName = $this->comicName; //iconv("UTF-8", "GBK//IGNORE", $this->comicName);die;//
		$gbkName && $this->comicName = $gbkName;
		$this->encodeName = rawurlencode($this->comicName);
		$this->comicList = self::$hostComicUrl . $this->encodeName . '/';
		isset($p['ridtype']) && $p['ridtype'] == 'any' && $this->ridType = 'any';
		$p['zip'] == 1 && $this->packageFlag = 1;
		$this->rids = $p['rids'] ? explode(',', $p['rids']) : FALSE;
		self::$saveType = self::$saveType == 'database' ? 'database' : 'file';
		self::$retryNum = self::$retryNum > 5 || self::$retryNum < 1 ? 0 : floor(self::$retryNum);
		$this->runMode = $p['runmode'] == 'background' ? 'background' : $this->runMode;
		self::$saveDir = self::$saveDir ? preg_replace('/\/$/', '', self::$saveDir) : 'jide_comic';
		(self::$threadNum > 5 || self::$threadNum < 1) && self::$threadNum = 3;
		$this->checkInfo = $p['checkinfo'] ? 1 : 0;
		$this->saveName = self::$zhFlag ? $this->comicTitle : $this->encodeName;
		self::$httpDatas = array();
	}

	/* 获取漫画卷集列表 */

	protected function getList() {
		$listHtml = gather($this->comicList, null, 0);
		self::$httpDatas['cookie'] = $listHtml[1][0];
		### 列表
		$lists = self::makeList($listHtml[0]);
		if (!$lists) die('error: get lists failed ! lsUrl: ' . $this->comicList);
		$this->lists = $lists;
		$this->rejectList($this->lists);
		### 标题
		$catTitle = '/<h1>([^<]+)<\/h1>/U';
		if (!preg_match($catTitle, $listHtml[0], $title)) {
			$cat404 = '/<title>404\s+ERROR/is';
			preg_match($cat404, $listHtml[0]) ? die("error: comicName($this->comicName) is undefined in jide !" . $this->comicList) : die('error: get title failed !' . $this->comicList);
		}
		$this->comicTitle = $title[1] ? $title[1] : $this->comicName;
		### 漫画家
		$authorCat = '/<em>原著作者：([^<]*)<\/em>/U';
		preg_match($authorCat, $listHtml[0], $author);
		$this->author = $author[1] ? $author[1] : 'Unkown~';
	}
	
	/* 获取漫画基本信息 */
	
	public static function getComicInfo($encodeName) {
		$url = self::$hostComicUrl . $encodeName . '/';
		$resHtml = gather($url);
		//$resHtml = gather($url ,null , 3); //
		$time = time();
		$infoArr = array();
		//$cat = '/<div\s+class="title"><h1>([^<]*)<\/h1><span>&nbsp;更新至:\s*<a\s+href="[^"]*"\s+target="_blank"\s+title="[^"]*"\s+class="[^"]*">[^<]*<\/a><script>.*<\/script><\/span><\/div><div\s+class="[^"]*">\s*<p\s+class="[^"]*"><em>更新时间：<\/em><span\s+class="[^"]*">([^<]*)<\/span><\/p>\s*<p\s+class="[^"]*"><em>原著作者：<\/em>([^<]*)<\/p>\s*<p\s+class="[^"]*"><em>加入时间：\s*<\/em>[^<]*<\/p>\s*<p\s+class="[^"]*"><em>字母索引：<\/em><a\s+href="[^"]*">[^<]*<\/a><\/p>\s*<p\s+class="[^"]*"><em>剧情类别：<\/em><a\s+href="[^"]*">[^<]*<\/a><\/p>\s*<p\s+class="[^"]*"><script[^>]*><\/script><\/p>\s*<p\s+class="[^"]*"><em>[^<]*<\/em>[^<]*<\/p>\s*<p\s+class="[^"]*"><script[^>]*><\/script><\/p>\s*<p\s+class="[^"]*"\s*id="[^"]*"><em>简　　介：<\/em>([^<]*)(?:<a[^>]*>[^<]*<\/a>)<\/p>/Ui'; // [1]title [2]update [3]author [4]brief
		$cat = '/<div\s+class="title">\s*<h1>([^<]*)<\/h1>\s*<span>[\s\S]*更新至:\s*<a\s+href="[^"]*"\s+target="_blank"\s+title="[^"]*"\s+class="[^"]*">[^<]*<\/a>\s*(?:<script>.*<\/script>)?<\/span>\s*<\/div>\s*<div\s+class="[^"]*">\s*<p\s+class="[^"]*">\s*<em>更新时间：<\/em>\s*<span\s+class="[^"]*">([^<]*)<\/span>\s*<\/p>\s*<p\s+class="[^"]*">\s*<em>原著作者：<\/em>([^<]*)<\/p>\s*<p\s+class="[^"]*">\s*<em>加入时间：\s*<\/em>[^<]*<\/p>\s*<p\s+class="[^"]*">\s*<em>字母索引：<\/em><a\s+href="[^"]*">[^<]*<\/a>\s*<\/p>\s*<p\s+class="[^"]*">\s*<em>剧情类别：<\/em><a\s+href="[^"]*">[^<]*<\/a>\s*<\/p>\s*<p\s+class="[^"]*">\s*<script[^>]*><\/script><\/p>\s*<p\s+class="[^"]*"><em>[^<]*<\/em>[^<]*<\/p>\s*<p\s+class="[^"]*">\s*<script[^>]*><\/script><\/p>\s*<p\s+class="[^"]*"\s*id="[^"]*">\s*<em>简　　介：<\/em>\s*([^<]*)(?:<a[^>]*>[^<]*<\/a>)\s*<\/p>/Ui';
		$thumbCat = '/<div\s+class="info_cover">\*<p\s+class="cover"\s+style="[^"]*">\*<img\s+src="([^"]*)"\s+class="pic"/Ui';
		if (!preg_match($cat, $resHtml[0], $foo)) {
			die('{"status": "false", "msg": "get info failed !"}');
		} else {
			//echo '<pre>' . var_export( $foo, true) . '</pre>';
		}
		preg_match($thumbCat, $resHtml[0], $thumbFoo);
		
		$infoArr = array(
			'author' => $foo[3],
			'update' => $foo[2],
			'title' => $foo[1],
			'state' => '未知',
			'brief' => $foo[4],
			'thumb' => $thumbFoo[1],
			'time' => $time,
			'rtime' => $time,
			'comicName' => $encodeName,
			'lists' => self::makeList($resHtml[0]),
			'status' => 'true'
		);
		
		foreach ($infoArr AS $k => $v) {
			switch ($k) {
				case 'author':
				case 'title':
				case 'state':
				case 'brief':
					$infoArr[$k] = rawurlencode($v); //mb_convert_encoding($v, 'UTF-8', 'CP936')
					break;
				case 'lists':
					$listsArr = array();
					foreach ($v AS $kl => $vl) {
						$volName = $vl[2]; //mb_convert_encoding($vl[2], 'UTF-8', 'CP936');
						$listsArr[] = array('vol' => $kl, 'volName' => $volName);
					}
					$infoArr[$k] = $listsArr;
					break;
			}
		}
		return $infoArr;
	}

	/* 提取卷集列表 */

	public static function makeList($listHtml) {		
		$cat = '/<li(?:[^>]+)?>\s*<a\s+href="[^"]*(.manhua.[^"]+)"\s+title="[^"]+"\s+target="_blank">\s*?([^<]+)\s*?(?:<img\s+src="[^"]+"\s*.?>)?<.a>\s*<.li>/U';
		if (!preg_match_all($cat, $listHtml, $list_arr, 2)) return FALSE;
		foreach ($list_arr AS $k => $v){
			$list_arr[$k][1] = self::$url_gather['index_web'] . $v[1];
		}
		$lists = array_reverse($list_arr); // 顺序排列
		return $lists;
	}

	/* 筛选下载卷集列表 */

	protected function rejectList($list_arr) {
		$lists = $listsTemp = $list_arr;
		if (!$this->rids && $this->ridType == 'all') {
			$this->listCount = count($lists);
			return $this->listData = $lists;
		} else if (!$this->rids && $this->ridType == 'any') {
			die('error: no lists be choosed !');
		}
		$lists_bak = array();
		foreach ($this->rids AS $v) {
			$vi = $v - 1;
			if ($vi < 0) continue;
			if ($listsTemp[$vi]) {
				$lists_bak[$vi] = $listsTemp[$vi];
				unset($lists[$vi]);
			}
		}
		$this->listData = $this->ridType == 'all' ? $lists : $lists_bak;
		!$this->listData && die('error: after reject lists is null !');
		$this->listCount = count($this->listData);
	}

	/* 采集漫画 */

	protected function getComicByLists($listData = null) {
		$listData = $listData ? $listData : $this->listData;
		$this->comicPicTotal($listData);
		$stopFile = self::$saveDir . '/' . $this->saveName . '/stop.txt';
		file_exists($stopFile) && unlink($stopFile); // 删除停止
		if ($this->runMode == 'foreground') {
			//set_time_limit(5110);
			$this->outputInfo($listData);
			file_put_contents($this->dataPath, serialize($this->comicData)); // 存储数据到文件
			$threadQueue = $this->getMultiprocess($listData);
			//file_put_contents('url.txt', var_export($threadQueue, TRUE));
			$this->_flock($this->dataPath); // 锁定
			foreach ($threadQueue AS $v) {
				file_exists($stopFile) && die(); // 停止
				if (!$v) continue;
				$host = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
				$rids = rawurlencode($v);
				$saveType = self::$saveType;
				$threadUrl = $host . "?a=down&n={$this->encodeName}&t=any&s={$saveType}&r=$rids&u=background&z={$this->packageFlag}";
				//echo $threadUrl . '<br>';//
				//file_put_contents('url.txt', $threadUrl);
				$datas['timeout'] = 3;
				gather($threadUrl, $datas);
			}
			$this->_flock($this->dataPath, 0); // 解锁
		} else if ($this->runMode == 'background') {
			//file_put_contents('log.txt', var_export($listData, TRUE));
			foreach ($listData AS $k => $v) {
				$this->comicDownloader($k);
				$this->packageComic($k);
			}
		}
	}

	/* 设置进程批次 */

	protected function getMultiprocess($listData) {
		$listNum = count($listData);
		$listFoo = array();
		foreach ($listData AS $k => $v) {
			$listFoo[] = $k + 1;
		}
		$listFoo || die('error: $listFoo is Null !');
		$threadQueue = array();
		$loopFlag = TRUE;
		$q = 0;
		foreach ($listFoo AS $v) {
			++$q;
			$q = $q >= self::$threadNum ? 0 : $q;
			$threadQueue[$q][] = $v;
		}
		foreach ($threadQueue AS $p => $n) {
			$n && $threadQueue[$p] = implode(',', $n);
		}
		return $threadQueue;
	}

	/* 打包漫画 */

	protected function packageComic($k) {
		if (!$this->packageFlag) return FALSE;
		//include WEB_PATH . '/class/pclzip.lib.php';
		loadClass('pclzip.lib');
		$fileCount = count($this->comicData['data'][$k]['fileNames']);
		$picCount = count($this->comicData['data'][$k]['pics']);
		if ($fileCount < 1 || $picCount != $fileCount) return FALSE;
		$packageDir = self::$saveDir . '/package/' . $this->saveName . '/';
		file_exists($packageDir) || (mkdir($packageDir, 0777, TRUE) || die('error: make dir(' . $packageDir . ') failed !'));
		$fileNames = implode(',', $this->comicData['data'][$k]['fileNames']);
		$packName = $packageDir . $this->comicData['data'][$k]['dirVol'] . '.zip';
		if (file_exists($packName)) return FALSE;
		file_put_contents($packName, ''); // 占位防重复
		$archive = new PclZip($packName);
		$doPackage = $archive->create($fileNames, PCLZIP_OPT_REMOVE_PATH, self::$saveDir);
	}

	/* 输出预览信息 */

	protected function outputInfo($listData) {
		$lists_arr = array();
		foreach ($listData AS $k => $v) {
			$lists_arr[] = ++$k;
		}
		$lists = implode(',', $lists_arr);
		$saveName = rawurlencode($this->saveName);
		$checkFile = self::$saveDir . '/' . $saveName . '/?';
		$packageDir = self::$saveDir . '/package/' . $saveName . '/?';
		$json = 'xindm.init({
			"listCount": "' . $this->listCount . '",
			"comicTitle": "' . $this->comicTitle . '",
			"comicName": "' . $this->comicName . '",
			"picCount": "' . $this->picCount . '",
			"checkFile": "' . $checkFile . '",
			"dataPath": "' . $this->dataPath . '",
			"zipPackage": "' . $packageDir . '",
			"lists": "' . $lists . '"
		});';
		###
		header("Content-type:text/html;charset=GB2312");
		echo str_repeat(" ", 4096);
		echo "<script>$json</script>";
		ob_flush() . flush();
	}

	/* 获取所有卷集图片 */

	protected function comicPicTotal($listData) {
		foreach ($listData AS $k => $v) {
			$this->getComicData($k, $v);
			!$this->retryFlag && $this->picCount += count($this->comicData['data'][$k]['pics']);
		}
	}

	/* 获取漫画卷集数据 */

	protected function getComicData($k, $v) {
		if ($this->comicData['data'][$k]) return TRUE;
		$this->comicData['data'][$k] = array();
		$listUrl = $this->getListUrl($k, $v);
		self::$httpDatas['referer'] = $this->comicList;
		$picLists = gather($listUrl, self::$httpDatas);
		$picLists[0] || $picLists = gather($listUrl, self::$httpDatas); // 二次获取
		$picLists[0] || die('error: picList html [' . ($k + 1) . '] is null !' . $picLists[0] . $listUrl);
		$picLists[1][0] && $this->comicData['data'][$k]['cookie'] = self::$httpDatas['cookie'] = $picLists[1][0];
		//$picListCat = '/var\s+ArrayPhoto=new\s+Array\(((?:"[^"]+",?)+)\);/U';
		$picListCat = '/var\s+qTcms_S_m_murl_e\d?\s*=\s*"([^"]+)";/U';
		$picListCat1 = '/var\s+qTcms_S_m_murl_e\d?\s*=\s*"([^"]*)";/U';
		if (!preg_match($picListCat, $picLists[0], $pics_arr)) {
			if (!preg_match($picListCat1, $picLists[0])) {
				die('error: get pics null on list[' . ($k + 1) . '] !' . $listUrl . '<!-- ' . htmlspecialchars($picLists[0]) . '-->');
			} else {
				return $this->comicData['data'][$k]['pics'] = array(self::$hostUrl. '15227/201343193941916.jpg'); // 尸兄
			}
		}
		//$pics_str = str_replace('"', '', $pics_arr[1]);
		$pics_str = base64_decode($pics_arr[1]);
		$pics = explode('$qingtiandy$', $pics_str);
		$pics || die('error: [' . $k . '] pics is null !');
		$this->comicData['data'][$k]['pics'] = $pics;
	}

	/* 提取卷集url */

	protected function getListUrl($k, $v) {
		//echo $v[1].'<br>';
		if ($this->comicData['data'][$k]['listUrl']) return $this->comicData['data'][$k]['listUrl'];
		/*$listCat = '/(.*xindm\.cn\/mh\/)((?:[^\/]+\/)?)([^\/]+\.[\s\S]+$)/U';
		if (preg_match($listCat, $v[1], $url_foo)) {
			$url_foo[2] = rawurlencode($url_foo[2]); //print_r($url_foo);
			$listUrl = $url_foo[1] . $url_foo[2] . $url_foo[3];
		} else {
			die('error: get list[' . ($k + 1) . '] url failed !');
			$listUrl = preg_replace($this->comicName, $this->encodeName, $v[1]);
		}*/
		$listUrl = $v[1];
		return $this->comicData['data'][$k]['listUrl'] = $listUrl;
	}

	/* 设置漫画卷集数据 */

	protected function setComicData() {
		$dataPath = $this->dataPath = self::$saveDir . '/' . $this->saveName . '/lists_bak.txt';
		$this->comicData = array();
		if (!self::$debug && file_exists($dataPath)) {
			$comicData_foo = file_get_contents($dataPath);
			$comicData_temp = unserialize($comicData_foo);
			$time = time();
			is_array($comicData_temp) && $time - $comicData_temp['time'] < 3600 * 2 && $this->comicData = $comicData_temp;
		}
		if (!$this->comicData) {
			$this->comicData = array(
				'title' => $this->comicTitle,
				'author' => $this->author,
				'name' => $this->comicName,
				'lists' => $this->lists,
				'data' => array(),
				'time' => $time
			);
			if (!file_exists($dataPath)) {
				$parent = dirname($dataPath);
				!file_exists($parent) && (mkdir($parent, 0777, TRUE) || die('error: make dir(' . $parent . ') fail !'));
				file_put_contents($dataPath, ''); // 创建列表数据记录文件
			}
		}
		return TRUE;
	}

	/* 采集器 */

	protected function comicDownloader($no) {
		$packs = 0;
		self::$httpDatas['cookie'] = $this->comicData['data'][$no]['cookie'];
		self::$httpDatas['referer'] = $this->comicData['data'][$no]['listUrl'];
		$pics = $this->retryFlag ? $this->comicData['data'][$no]['retry'] : $this->comicData['data'][$no]['pics'];
		$this->comicData['data'][$no]['retry'] = FALSE;
		$cat = '/\/(.*?)\/([^\/]+)\.(\w+)$/is';
		$dirVol = $this->getDirVol($cat, $pics[0], $no);
		$volPath = $this->getVolPath($dirVol);
		$this->addViewFile($volPath);
		$this->unique('set', $volPath); // 设置唯一标识
		foreach ($pics AS $k => $v) {
			$stopFile = self::$saveDir . '/' . $this->saveName . '/stop.txt';
			file_exists($stopFile) && die; // 停止
			$runAgain = $this->unique('get', $volPath); // 验证唯一标识
			if (!$runAgain) {
				file_put_contents('weiyi.txt', $volPath, 8);
				break;
			}
			$path = $this->getPicsPath($cat, $dirVol, $k, $v, $no, $volPath);
			if ($path === TRUE) continue;
			$img = gather($path['imgUrl'], self::$httpDatas);
			$save = $this->saveComic($img, $v, $path, $no, $dirVol);
			$save && ++$packs;
		}
		$this->imgCount += $packs;
	}

	/* 提取卷集目录 */

	protected function getDirVol($cat, $pic, $no) {
		//echo $cat, $pic, $no;
		$dirVol = array();
		preg_match($cat, $pic, $dir_foo) || die('error: get dir info false !' . $pic);
		$dir_arr = explode('/', $dir_foo[1]);
		$dirVol[] = array_pop($dir_arr); // 卷/集数 目录
		$dirVol[] = $this->getDir($no, $dirVol[0]);
		return $dirVol;
	}

	/* 生成图片路径/url */

	protected function getPicsPath($cat, $dirVol, $k, $v, $no, $volPath) {
		preg_match($cat, $v, $img_foo) || die('error: get img info fail !');
		$path = array();
		$path['dir'] = $volPath;
		$number = ++$k + 1000;
		$path['picName'] =  $number . "_" . $img_foo[2] . '.' . $img_foo[3];
		$path['fileName'] = $path['dir'] . $path['picName'];
		if (!$this->retryFlag) {
			$this->comicData['data'][$no]['fileNames'][] = $path['fileName'];
			$this->comicData['data'][$no]['dirVol'] || $this->comicData['data'][$no]['dirVol'] = $dirVol[1];
		}
		###
		$passFlag = FALSE; // 跳过标记
		file_exists($path['fileName']) && $passFlag = TRUE;
		if ($passFlag && self::$saveType == 'database') {
			$this->checkSQLrecord($dirVol, $v) || $passFlag = FALSE;
		}
		if ($passFlag === TRUE) {
			!$this->retryFlag && $this->passImgs[] = $path['fileName'];
			return TRUE;
		}
		//$imgurl = self::$hostUrl . $v;
		$imgurl = $v;
		$path['imgUrl'] = preg_replace('/\s/', '%20', trim($imgurl));
		return $path;
	}

	/* 生成卷集路径 */

	protected function getVolPath($dirVol) {
		$volPath = self::$saveDir . '/' . $this->saveName . '/' . $dirVol[1] . '/';
		if (!file_exists($volPath)) {
			mkdir($volPath, 0777, TRUE);
			file_exists($volPath) || die('error: make dir(' . $volPath . ') fail !');
		}
		### 漫画信息.txt
		if ($this->runMode == 'background') {
			$titlePath = self::$saveDir . '/' . $this->saveName . '/' . $this->comicTitle . '.txt';
			if (!file_exists($titlePath)) {
				$listNamesArr = array();
				foreach ($this->comicData['lists'] AS $k => $v) {
					$listNamesArr[] = '(' . ($k + 1) . ')' . $v[2];
				}
				$listNames = implode(', ', $listNamesArr);
				$infoContent = "漫画名称：" . $this->comicData['title'] . "\r\n"
						. "漫画作者：" . $this->comicData['author'] . "\r\n"
						. "漫画卷集：" . $listNames . "\r\n"
						. "漫画图片数：" . $this->picCount . "\r\n"
						. "生成时间：" . date('Y-m-d H:i:s');
				file_put_contents($titlePath, $infoContent);
			}
		}
		return $volPath;
	}

	/* 设置/读取 唯一标识符 */

	protected function unique($act, $volPath) {
		$uniqidPath = $volPath . 'uniqid.txt';
		if ($act == 'set') {
			$this->uniqid = uniqid();
			file_put_contents($uniqidPath, $this->uniqid) || die('error: write uniqid failed !');
		} else if ($act == 'get') {
			$uniqid = file_get_contents($uniqidPath);
			if ($this->uniqid != $uniqid) return FALSE;
		}
		return TRUE;
	}

	/* 增加view视图文件 */

	protected function addViewFile($path) {
		if (self::$zhFlag) return false;
		if (!$this->viewFile) {
			$viewFileStr = trim(file_get_contents('template/html/view.phtml'));
			$rootDir = dirname($_SERVER['PHP_SELF']);
			$homeUrl = 'http://' . $_SERVER['HTTP_HOST'] . $rootDir;
			$cssUrl = $homeUrl . '/template/css/view.css';
			$comicDir = $homeUrl . '/' . self::$saveDir;
			$viewFileStr = str_replace('{comicDir}', $comicDir, $viewFileStr);
			$viewFileStr = str_replace('{homeUrl}', $homeUrl, $viewFileStr);
			$viewFileStr = str_replace('{hostTitle}', Xindm_tool::$hostTitle, $viewFileStr);
			$this->viewFile = str_replace('{view.css}', $cssUrl, $viewFileStr);
		}
		$packageDir = self::$saveDir . '/package/' . $this->saveName . '/';
		$packageAllDir = self::$saveDir . '/package/';
		$packageView = $packageDir . 'index.php';
		$packageAllView = $packageAllDir . 'index.php';
		file_exists($packageDir) || (mkdir($packageDir, 0777, TRUE) || die('error: make dir(' . $packageDir . ') failed !'));
		file_exists($packageAllDir) || (mkdir($packageAllDir, 0777, TRUE) || die('error: make dir(' . $packageAllDir . ') failed !'));
		$picView = $path . 'index.php';
		$volView = dirname($path) . '/index.php';
		$comicView = dirname(dirname($path)) . '/index.php';
		file_exists($packageView) || file_put_contents($packageView, $this->viewFile);
		file_exists($volView) || file_put_contents($volView, $this->viewFile);
		file_exists($picView) || file_put_contents($picView, $this->viewFile);
		file_exists($comicView) || file_put_contents($comicView, $this->viewFile);
		file_exists($packageAllView) || file_put_contents($packageAllView, $this->viewFile);
	}

	/* 检查数据库记录是否存在 */

	protected function checkSQLrecord($dirVol, $v) {
		//$sql_check = "SELECT id FROM comic_data WHERE comic_name = '$this->comicName' AND vol_name = '$dirVol[1]' AND pic_url = '$v' LIMIT 1";
		//die($sql_check);
		$db = System::database();
		$check_res = $db->select('comic_data', 'id', ['comic_name' => $this->comicName, 'pic_url' => $v, 'vol_name' => $dirVol[1], 'LIMIT' => 1]);
		if (!$check_res) return FALSE;
		return TRUE;
	}

	/* 设置卷集目录 */

	protected function getDir($no, $dir) {
		$no += 1;
		if ($no < 10) {
			$no = '000' . $no;
		} else if ($no < 100) {
			$no = '00' . $no;
		} else if ($no < 1000) {
			$no = '0' . $no;
		}
		return 'JD_' . $no . '_' . $dir;
	}

	/* 存储器 */

	protected function saveComic($imgData, $imgName, $path, $no, $dirVol) {
		$saveFlag = TRUE; // 成功保存标记
		//$lengthCat = '/Content-Length:\s+(\d+)$/';
		//preg_match($lengthCat, $imgData[2][3], $dataLength) || $saveFlag = FALSE;
		$saveFlag && $imgLength = strlen($imgData[0]);
		isset($_GET['debug']) && $_GET['debug'] && print($imgLength . '<br>');
		if (/*!$saveFlag || $imgData[2][0] != 'HTTP/1.1 200 OK' || */$imgLength < 1000 /*|| $imgLength != $dataLength[1]*/) {
			$saveFlag = FALSE;
			isset($_GET['debug']) && $_GET['debug'] && print_r($imgData[2]);die('99999');
		}
		if ($saveFlag == FALSE) {
			if (self::$debug) {
				die("error: get file '$fileName' false !");
			} else {
				$this->comicData['data'][$no]['retry'][] = $imgName;
				!$this->retryFlag && $this->rePics[] = $imgName;
			}
			return FALSE;
		}
		### 保存到数据库
		if (self::$saveType == 'database') {
			//$date = date('Y-m-d H:i:s');
			$db = System::database();
			$date = time();
			$contTypeCat = '/Content-Type:\s+([\s\S]+)$/i';
			if ($this->checkSQLrecord($dirVol, $imgName)) {
				$sql_delete = "DELETE FROM comic_data WHERE comic_name = '$this->comicName' AND vol_name = '$dirVol[1]' AND pic_url = '$imgName' LIMIT 1";
				$db->query($sql_delete) || die('error: delete pic (' . $imgName . ') fail !');
			}
			preg_match($contTypeCat, $imgData[2][2], $contTypeArr); // || die('error: get contentType of img fail !'); // 图片格式类型
			$contType = $contTypeArr[1] ? $contTypeArr : 'image/jpeg';
			$pic_data = addslashes($imgData[0]); //base64_encode(
			$sql_insert = "INSERT INTO comic_data (comic_title, comic_name, vol_name, pic_name, pic_url, pic_type, pic_data, add_time) VALUES ('$this->comicTitle', '$this->comicName', '$dirVol[1]', '{$path['picName']}', '$imgName', '$contType[1]', '$pic_data', '$date')";
			$q = $db->query($sql_insert);
			!$q && die('mysql_error: ' . $sql_insert . '<br />' . mysql_error());
		}
		### 保存到文件
		//print_r($path);die;
		$res = file_put_contents($path['fileName'], $imgData[0]);
		if (!$res) {
			//file_put_contents('debug.txt', $path['fileName'] . '----' . $imgData[0] . "\n", 8);
			if (!self::$debug) {
				$this->comicData['data'][$no]['retry'][] = $imgName;
				$this->rePics[] = $imgName;
				return FALSE;
			}
			die("error: save file '{$path['fileName']}' failed !");
		}
		return TRUE;
	}

	/* 重试失败下载 */

	protected function retryDownload() {
		$this->retryFlag = TRUE;
		for ($i = 0; $i < self::$retryNum; $i++) {
			$listData = array();
			foreach ($this->comicData['data'] AS $k => $r) {
				if (is_array($r['retry'])) $listData[$k] = $r['listUrl'];
			}
			if (!$listData) {
				break;
			} else {
				$this->getComicByLists($listData);
			}
			++$this->retryCount;
		}
	}

	/* 文件锁/防重复运行 */

	protected function _flock($filePath, $status = 1) { // status: [1]锁定 [0]解锁
		if (!self::$debug) return FALSE;
		if ($status > 0) {
			!file_exists($filePath) && die('error: lock file is undefined !');
			$this->fpLock = fopen($filePath, "w+");
			if (!flock($this->fpLock, LOCK_EX)) die("error: Couldn't lock the file !"); // 进行排它型锁定
		} else if ($this->fpLock != FALSE) {
			flock($this->fpLock, LOCK_UN); // 释放锁定
			fclose($this->fpLock);
		}
	}

	/* Ajax 按名称搜索漫画 */

	static public function searchComic($comicName, $page = 1, $stype = 0) { // stype[0]web [1]mobile

		if ($comicName == '') return '{"status": "false", "msg": "comicName is null !"}';
		### 搜索
		$res = $stype ? self::_getSearchByMobile($comicName, $page) : self::_getSearchByWeb($comicName, $page);
		//echo '<pre>' . var_export($res , true) . '</pre>';
		### 匹配
		$catListWeb = '/<li>\s*<p\s*class="[^"]*">\s*<a\s+href="[^"]*"\s+class="[^"]*">\s*<img\s+src="([^"]+)"\s+alt="[^"]*"\s*\/?><\/a>\s*<span>\s*<a\s+class="[^"]*"\s+href="[^"]*"\s+target="_blank">[^"]+<\/a>\s*<\/span>\s*<\/p>\s*<dl>\s*<dt><a\s+href="\/manhua\/(\d+)\/"\s+title="([^"]+)">[^<]+<\/a><\/dt>\s*<dd><p>\s*<em>[^<]+<\/em>\s*<span>(?:<font\s*color=[^>]+>)?([^<]+)(?:<\/font>)?<\/span>\s*<\/p>\s*<p>\s*<em>[^<]+<\/em>\s*<span\s+class="[^"]+">([^<]+)<\/span>\s*<\/p>\s*<p>\s*<em>类　别：<\/em>\s*<a href="[^"]+" target="_blank">[^<]+<\/a>\s*<\/p>\s*<p\s+class="intro">\s*<em>简　介：<\/em>([\s\S]*)<\/p>\s*<\/dd>\s*<\/dl>/Ui';

		$catListWap = '/<li>\s*<div\s+class="[^"]*">[^<]+<\/div>\s*<a\s+href="\/manhua\/(\d+)\/">\s*<div\s+class="[^"]*">\s*<img\s+data-src="([^"]+)"\s*\/>\s*<i>\s*([^<]+)\s*<\/i>\s*<\/div>\s*<h3>([^<]+)<\/h3>\s*<dl>\s*<dt>作　者：<\/dt>\s*<dd>([^<]+)<\/dd>\s*<\/dl>\s*<dl>\s*<dt>类　别：<\/dt>\s*<dd>[^<]*<\/dd>\s*<\/dl>\s*<dl>\s*<dt>更新至：<\/dt>\s*<dd>[^<]*<\/dd>\s*<\/dl>\s*<dl>\s*<dt>更新于：<\/dt>\s*<dd>([^<]+)<\/dd>\s*<\/dl>\s*<\/a>\s*<\/li>/Ui';
		$catList = $stype ? $catListWap : $catListWeb;
		if (!preg_match_all($catList, $res[0], $foo, 2)) return '{"status": "false", "msg": "no res !! ", "count": "0"}';
		$arr = array();
		foreach ($foo AS $k => $v) {
			// web: 1 thumb 2 name 3 title 4 time 5 state 6 brief
			// mob: 1 name 2 thumb 3 state 4 title 5 brief 6 time
			$paramArr = !$stype ? array(
				'thumb' => 1,
				'name' => 2,
				'title' => 3,
				'time' => 4,
				'state' => 5,
				'brief' => 6
			) : array(
				'thumb' => 2,
				'name' => 1,
				'title' => 4,
				'time' => 6,
				'state' => 3,
				'brief' => 5
			); 
			$thumb = rawurldecode($v[$paramArr['thumb']]);
			$_title = $v[$paramArr['title']]; //mb_convert_encoding($v[$paramArr['title']], 'UTF-8', 'CP936');
			$title = rawurlencode($_title);
			$name = rawurlencode($v[$paramArr['name']]); //mb_convert_encoding($v[1], 'GB2312', 'UTF-8');
			$_state = $v[$paramArr['state']]; //mb_convert_encoding($v[$paramArr['state']], 'UTF-8', 'CP936');
			$state = rawurlencode($_state);
			$updateTime = $v[$paramArr['time']];
			$_brief = $v[$paramArr['brief']]; //mb_convert_encoding($v[$paramArr['brief']], 'UTF-8', 'CP936');
			$brief = str_replace('"', '&quot;', $_brief);
			$brief = rawurlencode($_brief);
			$arr[] = '{"thumb": "' . $thumb . '", "title": "' . $title . '", "comicName": "' . $name . '", "state": "' . $state . '", "update": "' . $updateTime . '", "brief": "' . $brief . '"}';
		}
		### 统计
		$catTotal = $stype ? '/共有(\d+)条符合/' : '/共\s*(\d+)\s*部搜索漫画/';
		
		preg_match($catTotal, $res[0], $totalArr);
		$total = $totalArr[1] ? $totalArr[1] : count($foo);
		### 整理
		$arrStr = implode(', ', $arr);
		$time = time();
		$json = '{"status": "true", "time": ' . $time . ', "page": ' . $page . ', "total": "' . $total . '", "lists": [' . $arrStr . ']}';
		return $json;
	}
	
	/* 通过电脑版获取正文 */
	
	static public function _getSearchByWeb($comicName, $page) {
		/*$datas['post'] = array(
			'key' => $comicName,
			'button' => '搜索漫画', // %CB%D1%CB%F7%C2%FE%BB%AD
		);*/
		session_start();
		$datas['referer'] = self::$url_gather['index_web'] . '/';
		$_SESSION['cookie'] && $datas['cookie'] = $_SESSION['cookie'];
		### cookie
		//$pre = gather($datas['referer']);
		//$datas['cookie'] = $pre[1][0];
		//$searchUrl = 'http://www.xindm.cn/e/search/index.php';
		//$searchUrl = 'http://www.chuiyao.com/search.asp?page=' . $page . '&tag=0&key=' . rawurlencode($comicName) . '#ysearchs';
		$_comicName = rawurlencode($comicName);
		$searchUrl = self::$url_gather['search_web'];
		$datas['post'] = array(
			//'' // TODO
			'button' => '搜索漫画',
			'key' => $_comicName
		);
		// 翻页
		if ($page > 1) {
			$searchUrl .= '?key=' . $_comicName . '&page=' . $page;
			$datas = null;
		}
		$res = gather($searchUrl, $datas);
		$_SESSION['cookie'] = $res[1][0];
		$_COOKIE['search'] || setcookie('search', $datas['cookie'], time() + 3600);
		//$res = gather($searchUrl, '', 3); // debug
		if (!$res[0]) return '{"status": "false", "msg": "error: get the url(' . $searchUrl . ') failed !", "count": "0"}';
		return $res;
	}
	
	/* 通过移动版获取正文 */
	
	static public function _getSearchByMobile($_comicName, $page) {
		
		$comicName = rawurlencode($_comicName);
		$searchUrl = $page > 1 ? self::$url_gather['search_wap'] . '?page=' . $page . '&ajax=1&order=1&key=' . $comicName . '&act=search' : self::$url_gather['search_wap'] . '/q_' . $comicName;
		$res = gather($searchUrl);
		return $res;
	}

	/* Ajax 查询下载情况 */

	public function checkPicAction() {
		if (!$this->checkInfo) return FALSE;
		//self::$saveType == 'database' && new Database;
		clearstatcache(); // 清除文件缓存
		$dataPath = isset($_GET['dataPath']) ? $_GET['dataPath'] : '';
		$lists_foo = isset($_GET['lists']) ? $_GET['lists'] : '';
		($dataPath && $lists_foo) || die('{"status": "false", "msg": "dataPath or lists_foo is error"}');
		$catPath = '/\/([^\/]+)\/lists_bak\.txt/U';
		preg_match($catPath, $dataPath, $dataPathName) || die('{"status": "false", "msg": "$dataPathName is undefined !"}');
		$dataPathStr = rawurlencode(rawurldecode($dataPathName[1]));
		$dataPath = str_replace($dataPathName[1], $dataPathStr, $dataPath);
		$lists = explode(',', $lists_foo);
		//print_r($lists);die('ppp');
		file_exists($dataPath) || die('{"status": "false", "msg": "no dataPath file ' . $dataPath . '"}');
		$dataFoo = file_get_contents($dataPath);
		$this->comicData = unserialize($dataFoo);
		//print_r($this->comicData);
		is_array($this->comicData) || die('{"status": "false", "msg": "comicData is not array"}');
		$downCount = 0;
		$listDownCount = 0;
		$listComplateArr = array(); // 完成卷集统计
		$listDownProgress = array(); // 卷集进度记录
		foreach ($lists AS $vi) {
			$no = $vi - 1;
			$pics = $this->comicData['data'][$no]['pics'];
			$cat = '/\/(.*?)\/([^\/]+)\.(\w+)$/is';
			$dirVol = $this->getDirVol($cat, $pics[0], $no);
			$volPath = $this->getVolPath($dirVol);
			$listComplete = 1;
			$listDownCount[$vi] = array();
			$listDownProgress[$vi]['total'] = $listDownProgress[$vi]['down'] = 0;
			foreach ($pics AS $k => $v) {
				++$listDownProgress[$vi]['total'];
				$path = $this->getPicsPath($cat, $dirVol, $k, $v, $no, $volPath);
				//print_r($path);
				if ($path === TRUE) {
					++$listDownProgress[$vi]['down'];
					++$downCount;
				} else {
					$listComplete = 0;
				}
			}
			$listComplete && $listComplateArr[] = $vi;
			$listDownCount += $listComplete;
		}
		$listDownProgressJsonStr = $this->makeVolProgress($listDownProgress);
		$listComplateNum = $listComplateArr ? implode(',', $listComplateArr) : 0;
		die('{"status": "true", "downCount": "' . $downCount . '", "listDownCount": "' . $listDownCount . '", "listComplateNum": "' . $listComplateNum . '", "listDownProgress": ' . $listDownProgressJsonStr . '}');
	}

	/* Ajax 暂停/继续 下载 */

	static public function downloadControl($comic) {
		if (!$comic) return '{"status:" "false", "msg": "comic is undefined !"}';
		$comic = rawurlencode(rawurldecode($comic));
		$stopFile = self::$saveDir . '/' . $comic . '/stop.txt';
		file_exists($stopFile) || file_put_contents($stopFile, time());
		return '{"status": "true", "msg": "create stop file success !"}';
	}

	/* 生成卷集下载进度数据 */

	protected function makeVolProgress($listDownProgress) {
		$listDownProgressJsonArr = array();
		foreach ($listDownProgress AS $n => $p) {
			$_volName = $this->comicData['lists'][$n - 1][2];
			//print_r($this->comicData);
			$volName = $_volName; //mb_convert_encoding($_volName, 'UTF-8', 'CP936');
			$listDownProgressJsonArr[] = '{"vol": "' . $n . '", "down": "' . $p['down'] . '", "total": "' . $p['total'] . '", "volName": "' . $volName . '"}';
		}
		$listDownProgressJsonStr = '[' . implode(',', $listDownProgressJsonArr) . ']';
		return $listDownProgressJsonStr;
	}
	
	/* Ajax 获取全部漫画名称 */
	
	public static function getAllComicNames() {
		$url = self::$url_gather['comic_all'];
		$res = gather($url);
		$cat = '/title="([^"]+)漫画"\s+class="tip_img\s+video"/Ui';
		if (!preg_match_all($cat, $res[0], $foo)) return false;
		/*foreach ($foo[1] AS $k => $v) {
			$foo[1][$k] = $v; //mb_convert_encoding($v, 'UTF-8', 'auto,CP936');
		}*/
		return $foo[1];
	}
}